#ifndef BTL_IMAGE_COLOURCONVERSION
#define BTL_IMAGE_COLOURCONVERSION

#include "PixelTypes.hpp"
#include "Image.hpp"

namespace btl
{
namespace image
{

template <class PixelTo, class PixelFrom>
struct PixelConverter;

template <class PixelT>
struct PixelConverter<PixelT, PixelT>
{
   PixelT operator()(const PixelT& x) const { return x; }
};

template <>
struct PixelConverter<PixelRGB, PixelGrey>
{
   PixelRGB operator()(const PixelGrey& from) const;
};

template <>
struct PixelConverter<PixelGrey, PixelRGB>
{
   PixelGrey operator()(const PixelRGB& from) const;
};

template <>
struct PixelConverter<PixelRGB, PixelFloat>
{
   PixelRGB operator()(const PixelFloat& from) const;
};

template <>
struct PixelConverter<PixelFloat, PixelRGB>
{
   PixelFloat operator()(const PixelRGB& from) const;
};

template <>
struct PixelConverter<PixelFloat, PixelGrey>
{
   PixelFloat operator()(const PixelGrey& from) const;
};

template <>
struct PixelConverter<PixelGrey, PixelFloat>
{
   PixelGrey operator()(const PixelFloat& from) const;
};

template <class ImageTo, class ImageFrom>
struct ImageConverter
{
   void operator()(ImageTo& to, const ImageFrom& from) const;
};

template <class ImageT>
struct ImageConverter<ImageT, ImageT>
{
   void operator()(ImageT& to, const ImageT& from) const { to.copy(from); }
};

// wrapper functions for ease of use
template <class To, class From>
To convert_pixel(const From& from);

template <class To, class From>
void convert_pixel(To& to, const From& from);

template <class To, class From>
void convert_image(To& to, const From& from);

}
}

namespace btl
{
using image::convert_pixel;
using image::convert_image;
}

// ====================================================================
// === Implementation

#include <cassert>

namespace btl
{
namespace image
{

// ~~~~~~~~~~ Grey -> RGB ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline PixelRGB PixelConverter<PixelRGB, PixelGrey>::operator()(const PixelGrey& from) const
{
   return PixelRGB(from);
}

// ~~~~~~~~~~ RGB -> Grey ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline PixelGrey PixelConverter<PixelGrey, PixelRGB>::operator()(const PixelRGB& from) const
{
   // these conversion factors are the Y' conversion factors
   // for JPEG Y'CbCr colour
   return (from.red() * 299 + from.green() * 587 + from.blue() * 114) / 1000;
}

// ~~~~~~~~~~ Float -> RGB ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline PixelRGB PixelConverter<PixelRGB, PixelFloat>::operator()(const PixelFloat& from) const
{
   return PixelRGB(static_cast<unsigned char>(from * 255.0f));
}

// ~~~~~~~~~~ RGB -> Float ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline PixelFloat PixelConverter<PixelFloat, PixelRGB>::operator()(const PixelRGB& from) const
{
   return (from.red() * 299 + from.green() * 587 + from.blue() * 114) / (1000.0f * 255.0f);
}

// ~~~~~~~~~~ Grey -> Float ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline PixelFloat PixelConverter<PixelFloat, PixelGrey>::operator()(const PixelGrey& from) const
{
   return (from / 255.0f);
}

// ~~~~~~~~~~ Float -> Grey ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inline PixelGrey PixelConverter<PixelGrey, PixelFloat>::operator()(const PixelFloat& from) const
{
   return static_cast<PixelGrey>(from * 255.0f);
}

// ~~~~~~~~~~ ImageConverter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

template <class ImageTo, class ImageFrom>
inline void ImageConverter<ImageTo, ImageFrom>::operator()(ImageTo& to, const ImageFrom& from) const
{
   assert(to.width() == from.width() && to.height() == from.height());

   typedef typename ImageTo::pixel_type to_pixel_type;
   typedef typename ImageFrom::pixel_type from_pixel_type;
   PixelConverter<to_pixel_type, from_pixel_type> cvt;

   // need to explicitly work on regions, unfortunately, because:
   // - somehow gcc doesn't find the [operator() const] in the image type,
   //   (I guess because it's a dependent type), and...
   // - SmallImage doesn't derive from ImageRegion, so you can't just
   //   explicitly tell the compiler to look up the operator()
   //   *from ImageRegion* (with from.const ImageRegion<T>::operator()(i,j))
   //   because that would be invalid if ImageFrom is a SmallImage<T>
   //   (but the conversion *is* valid because SmallImage provides an
   //    implicit conversion to ImageRegion)
   // FIXME: find another way of dealing with this problem
   const ImageRegion<const from_pixel_type>& fromregion = from;

   const int w = to.width();
   const int h = to.height();
   for(int i = 0; i < h; ++i)
   {
      for(int j = 0; j < w; ++j)
      {
         // need to explicitly tell it to use the const operator() for some reason
         //typedef const ImageRegion<const from_pixel_type> CT;
         to(i, j) = cvt(fromregion(i, j));
      }
   }
}

// ~~~~~~~~~~ conversion wrapper function ~~~~~~~~~~~~~~~~~~~~~~~~~~

template <class To, class From>
inline To convert_pixel(const From& from)
{
   PixelConverter<To, From> cvt;
   return cvt(from);
}

template <class To, class From>
inline void convert_pixel(To& to, const From& from)
{
   PixelConverter<To, From> cvt;
   to = cvt(from);
}

template <class To, class From>
inline void convert_image(To& to, const From& from)
{
   ImageConverter<To, From> cvt;
   cvt(to, from);
}

} // namespace image
} // namespace btl

#endif
